using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using System.Xml;

namespace AvalonDock
{
    public abstract class ManagedContent : ContentControl, INotifyPropertyChanged
    {
        #region Constants and Fields

        /// <summary>
        /// Gets or sets an element which is focused by default when content is activated
        /// </summary>
        public static readonly DependencyProperty DefaultFocusedElementProperty =
            DependencyProperty.Register("DefaultElement", typeof (IInputElement), typeof (ManagedContent));

        /// <summary>
        /// FloatingWindowSize Dependency Property
        /// </summary>
        public static readonly DependencyProperty FloatingWindowSizeProperty =
            DependencyProperty.Register(
                "FloatingWindowSize",
                typeof (Size),
                typeof (ManagedContent),
                new FrameworkPropertyMetadata(new Size(250, 400), OnFloatingWindowSizeChanged));

        /// <summary>
        /// Select an icon object for the content
        /// </summary>
        public static readonly DependencyProperty IconProperty = DependencyProperty.Register(
            "Icon", typeof (ImageSource), typeof (ManagedContent), new FrameworkPropertyMetadata(null, OnCoerce_Icon));

        public static readonly DependencyProperty IsCloseableProperty = DependencyProperty.Register(
            "IsCloseable", typeof (bool), typeof (ManagedContent), new UIPropertyMetadata(true));

        /// <summary>
        /// IsLocked Dependency Property
        /// </summary>
        public static readonly DependencyProperty IsLockedProperty = DependencyProperty.Register(
            "IsLocked", typeof (bool), typeof (ManagedContent), new FrameworkPropertyMetadata(false));

        public static readonly DependencyProperty TitleProperty = DependencyProperty.Register(
            "Title", typeof (string), typeof (ManagedContent));

        /// <summary>
        /// IsActiveContent Read-Only Dependency Property
        /// </summary>
        private static readonly DependencyPropertyKey IsActiveContentPropertyKey =
            DependencyProperty.RegisterReadOnly(
                "IsActiveContent",
                typeof (bool),
                typeof (ManagedContent),
                new FrameworkPropertyMetadata(false, OnIsActiveContentChanged));

        /// <summary>
        /// IsActiveDocument Read-Only Dependency Property
        /// </summary>
        private static readonly DependencyPropertyKey IsActiveDocumentPropertyKey =
            DependencyProperty.RegisterReadOnly(
                "IsActiveDocument",
                typeof (bool),
                typeof (ManagedContent),
                new FrameworkPropertyMetadata(false, OnIsActiveDocumentChanged));

        public static readonly DependencyProperty IsActiveContentProperty =
            IsActiveContentPropertyKey.DependencyProperty;

        public static readonly DependencyProperty IsActiveDocumentProperty =
            IsActiveDocumentPropertyKey.DependencyProperty;

        private FrameworkElement _dragEnabledArea;

        private DateTime _lastActivation = DateTime.MinValue;

        private DockingManager _manager;

        private bool isMouseDown;

        private Point ptRelativePosition;

        private Point ptStartDrag;

        #endregion

        #region Constructors and Destructors

        static ManagedContent()
        {
            //This OverrideMetadata call tells the system that this element wants to provide a style that is different than its base class.
            //This style is defined in themes\generic.xaml
            DefaultStyleKeyProperty.OverrideMetadata(
                typeof (ManagedContent), new FrameworkPropertyMetadata(typeof (ManagedContent)));

            WidthProperty.OverrideMetadata(
                typeof (ManagedContent),
                new FrameworkPropertyMetadata(
                    double.NaN, null, (s, v) => !DesignerProperties.GetIsInDesignMode(s) ? double.NaN : v));

            HeightProperty.OverrideMetadata(
                typeof (ManagedContent),
                new FrameworkPropertyMetadata(
                    double.NaN, null, (s, v) => !DesignerProperties.GetIsInDesignMode(s) ? double.NaN : v));

            FocusableProperty.OverrideMetadata(typeof (ManagedContent), new FrameworkPropertyMetadata(true));
        }

        protected ManagedContent()
        {
            GotFocus += (sender, args) =>
                {
                    Manager.ActiveContent = this;
                };
            Loaded += ManagedContent_Loaded;
            Unloaded += ManagedContent_Unloaded;
        }

        #endregion

        #region Public Events

        /// <summary>
        /// Event fired when the content has been closed
        /// </summary>
        /// <remarks>Note that when a document is closed property like <see cref="ManagedContent.ContainerPane"/> or <see cref="ManagedContent.Manager"/> returns null.</remarks>
        public event EventHandler Closed;

        /// <summary>
        /// Event fired when the content is about to be closed
        /// </summary>
        public event EventHandler<CancelEventArgs> Closing;

        /// <summary>
        /// Event fired when the <see cref="IsActiveContent"/> property changes
        /// </summary>
        public event EventHandler IsActiveContentChanged;

        /// <summary>
        /// Event fired when the <see cref="IsActiveContent"/> property changes
        /// </summary>
        public event EventHandler IsActiveDocumentChanged;

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets container pane currently hosting the content
        /// </summary>
        /// <remarks>Please note that this value could change as user move the content around the <see cref="DockingManager"/>.</remarks>
        public Pane ContainerPane
        {
            get
            {
                var containerPane = Parent as Pane;
                if (containerPane != null)
                {
                    return containerPane;
                }

                return this.FindVisualAncestor<Pane>(false);
            }
        }

        /// <summary>
        /// Access to <see cref="DefaultFocusedElementProperty"/>
        /// </summary>
        public IInputElement DefaultElement
        {
            get { return (IInputElement) GetValue(DefaultFocusedElementProperty); }
            set { SetValue(DefaultFocusedElementProperty, value); }
        }

        /// <summary>
        /// Gets or sets the FloatingWindowSize property.  This dependency property 
        /// indicates the size of the floating window hosting the content when it's floating.
        /// </summary>
        public Size FloatingWindowSize
        {
            get { return (Size) GetValue(FloatingWindowSizeProperty); }
            set { SetValue(FloatingWindowSizeProperty, value); }
        }

        /// <summary>
        /// Access to <see cref="IconProperty"/> dependency property
        /// </summary>
        public ImageSource Icon
        {
            get { return (ImageSource) GetValue(IconProperty); }
            set { SetValue(IconProperty, value); }
        }

        /// <summary>
        /// Gets the IsActiveContent property.  This dependency property 
        /// indicates the active (selected) content between all contents of the docking manager
        /// </summary>
        public bool IsActiveContent
        {
            get { return (bool) GetValue(IsActiveContentProperty); }
        }

        /// <summary>
        /// Gets the IsActiveDocument property.  This dependency property 
        /// indicates is content is the active document.
        /// </summary>
        public bool IsActiveDocument
        {
            get { return (bool) GetValue(IsActiveDocumentProperty); }
        }

        /// <summary>
        /// Get or set a value indicating if this content can be closed or hidden
        /// </summary>
        public bool IsCloseable
        {
            get { return (bool) GetValue(IsCloseableProperty); }
            set { SetValue(IsCloseableProperty, value); }
        }

        /// <summary>
        /// Gets or sets the IsLocked property.  This dependency property 
        /// indicates if this content is locked (for <see cref="DocumentContent"/> objects this often means that it's readonly).
        /// </summary>
        public bool IsLocked
        {
            get { return (bool) GetValue(IsLockedProperty); }
            set { SetValue(IsLockedProperty, value); }
        }

        /// <summary>
        /// Get current hosting docking manager (<see cref="DockingManager"/>)
        /// </summary>
        public DockingManager Manager
        {
            get { return _manager; }
            internal set
            {
                if (_manager != value)
                {
                    DockingManager oldValue = _manager;
                    _manager = value;
                    OnManagerChanged(oldValue, value);
                    RaisePropertyChanged("Manager");
                }
            }
        }

        /// <summary>
        /// Gets or set the title of the content
        /// </summary>
        public string Title
        {
            get { return (string) GetValue(TitleProperty); }
            set { SetValue(TitleProperty, value); }
        }

        #endregion

        #region Properties

        internal DateTime LastActivation
        {
            get { return _lastActivation; }
        }

        /// <summary>
        /// Gets the draggable area of the document
        /// </summary>
        protected FrameworkElement DragEnabledArea
        {
            get { return _dragEnabledArea; }
        }

        protected bool IsMouseDown
        {
            get { return isMouseDown; }
        }

        protected Point StartDragPoint
        {
            get { return ptStartDrag; }
        }

        #endregion

        #region Public Methods and Operators

        /// <summary>
        /// Set the content as the active content
        /// </summary>
        /// <remarks>After this method returns property <see cref="IsActiveContent"/> returns true.</remarks>
        public virtual void Activate()
        {
            if (Manager == null)
            {
                Manager = ContainerPane.GetManager();
            }

            if (ContainerPane != null && Manager != null) // && Manager.ActiveContent != this)
            {

                ContainerPane.SelectedItem = this;

                FocusContent();

                if (Manager != null)
                {
                    Manager.ActiveContent = this;
                }
            }
        }

        /// <summary>
        /// Close the content
        /// </summary>
        /// <returns>Returns true if the content was succesfully closed, false otherwise.</returns>
        public abstract bool Close();

        /// <summary>
        /// Hide the content
        /// </summary>
        public abstract bool Hide();

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            _dragEnabledArea = GetTemplateChild("PART_DragArea") as FrameworkElement;

            if (_dragEnabledArea != null)
            {
                _dragEnabledArea.MouseDown += OnDragMouseDown;
                _dragEnabledArea.MouseMove += OnDragMouseMove;
                _dragEnabledArea.MouseUp += OnDragMouseUp;
                _dragEnabledArea.MouseLeave += OnDragMouseLeave;
            }

            if (_dragEnabledArea != null)
            {
                _dragEnabledArea.InputBindings.Add(
                    new InputBinding(ManagedContentCommands.Close, new MouseGesture(MouseAction.MiddleClick)));
            }

            if (_dragEnabledArea != null && _dragEnabledArea.ContextMenu == null)
            {
                _dragEnabledArea.MouseRightButtonDown += (s, e) =>
                    {
                        if (!e.Handled)
                        {
                            Activate();
                            if (_dragEnabledArea.ContextMenu == null)
                            {
                                Dispatcher.BeginInvoke(
                                    DispatcherPriority.Input,
                                    new ThreadStart(delegate { ContainerPane.OpenOptionsMenu(null); }));
                            }
                            e.Handled = true;
                        }
                    };
            }
        }

        /// <summary>
        /// Restore content specific layout settings
        /// </summary>
        /// <param name="contentElement">The content element.</param>
        /// <remarks>
        /// Custom derived class must overload this method to restore custom layout settings previously saved trought <see cref="SaveLayout"/>.
        /// </remarks>
        public virtual void RestoreLayout(XmlElement contentElement)
        {
        }

        /// <summary>
        /// Save content specific layout settings
        /// </summary>
        /// <param name="storeWriter">Backend store writer</param>
        /// <remarks>Custom derived class can overloads this method to handle custom layout persistence.</remarks>
        public virtual void SaveLayout(XmlWriter storeWriter)
        {
        }

        /// <summary>
        /// Shows the content
        /// </summary>
        /// <remarks>How content is shows depends from the type of the content.</remarks>
        public abstract void Show();

        /// <summary>
        /// Shows the content inside a <see cref="DockingManager"/> object
        /// </summary>
        /// <remarks>How content is shows depends from the type of the content.</remarks>
        public abstract void Show(DockingManager manager);

        #endregion

        #region Methods

        internal virtual bool CanClose()
        {
            if (!IsCloseable)
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// Remove this content from its parent container pane
        /// </summary>
        /// <returns></returns>
        internal virtual ManagedContent DetachFromContainerPane()
        {
            if (ContainerPane != null)
            {
                int indexOfContent = ContainerPane.Items.IndexOf(this);
                return ContainerPane.RemoveContent(indexOfContent);
            }

            return null;
        }

        /// <summary>
        /// Provides a secure method for setting the IsActiveContent property.  
        /// This dependency property indicates the current content is the active content between all docking manager contents
        /// </summary>
        /// <param name="value">The new value for the property.</param>
        internal void SetIsActiveContent(bool value)
        {
            SetValue(IsActiveContentPropertyKey, value);
        }

        /// <summary>
        /// Provides a secure method for setting the IsActiveDocument property.  
        /// This dependency property indicates is content is the active document.
        /// </summary>
        /// <param name="value">The new value for the property.</param>
        internal void SetIsActiveDocument(bool value)
        {
            SetValue(IsActiveDocumentPropertyKey, value);
        }

        /// <summary>
        /// Retrive a value indicating if the command passed can be executed based to the content state
        /// </summary>
        /// <param name="command">Command to execute</param>
        /// <returns>True if the command can be execute, false otherwise.</returns>
        protected virtual bool CanExecuteCommand(ICommand command)
        {
            if (Manager == null)
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// Provides derived classes an opportunity to manage custom focus strategy.
        /// </summary>
        /// <remarks>
        /// Derived classes should not call base class if don't want AvalonDock to set focus on <see cref="DefaultElement"/> object
        /// </remarks>
        protected virtual void FocusContent()
        {
            if (IsActiveContent && !IsKeyboardFocused)
            {
                Dispatcher.BeginInvoke(
                    DispatcherPriority.Input,
                    new ThreadStart(
                        delegate
                            {
                                return;
                                if (IsActiveContent && !IsKeyboardFocused)
                                {
                                    if (DefaultElement != null)
                                    {
                                        Debug.WriteLine("Try to set kb focus to " + DefaultElement);

                                        var kbFocused = Keyboard.Focus(DefaultElement);

                                        if (kbFocused != null)
                                        {
                                            Debug.WriteLine("Focused element " + kbFocused);
                                        }
                                        else
                                        {
                                            Debug.WriteLine("No focused element");
                                        }
                                    }
                                    else if (Content is UIElement)
                                    {
                                        Debug.WriteLine("Try to set kb focus to " + Content);
                                        (Content as UIElement).Focus();
                                        var kbFocused = Keyboard.Focus(Content as IInputElement);
                                        if (kbFocused != null)
                                        {
                                            Debug.WriteLine("Focused element " + kbFocused);
                                        }
                                        else
                                        {
                                            Debug.WriteLine("No focused element");
                                        }
                                    }
                                }
                            }));
            }
        }

        protected DockingManager GetParentManager(Pane containerPane)
        {
            if (containerPane == null)
            {
                containerPane = ContainerPane;
            }

            return containerPane != null ? ContainerPane.GetManager() : null;
        }

        /// <summary>
        /// Called when [closed].
        /// </summary>
        protected virtual void OnClosed()
        {
            if (Closed != null)
            {
                Closed(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Ovveride this method to handle <see cref="DocumentContent.OnClosing"/> event.
        /// </summary>
        protected virtual void OnClosing(CancelEventArgs e)
        {
            if (Closing != null && !e.Cancel)
            {
                Closing(this, e);
            }
        }

        protected virtual void OnContentLoaded()
        {
            RaisePropertyChanged("ContainerPane");
        }

        protected virtual void OnContentUnloaded()
        {
            RaisePropertyChanged("ContainerPane");
        }

        protected virtual void OnDragMouseDown(object sender, MouseButtonEventArgs e)
        {
            Debug.WriteLine("OnDragMouseDown" + e.ClickCount);
            if (!e.Handled && Manager != null)
            {
                isMouseDown = true;
                ptStartDrag = e.GetPosition((IInputElement) VisualTreeHelper.GetParent(this));
            }
        }

        protected virtual void OnDragMouseLeave(object sender, MouseEventArgs e)
        {
            if (!e.Handled && isMouseDown && e.LeftButton == MouseButtonState.Pressed && Manager != null)
            {
                if (!IsMouseCaptured)
                {
                    var ptMouseMove = e.GetPosition((IInputElement) VisualTreeHelper.GetParent(this));
                    ManagedContent contentToSwap = null;
                    if (ContainerPane != null)
                    {
                        foreach (ManagedContent content in ContainerPane.Items)
                        {
                            if (Equals(content, this))
                            {
                                continue;
                            }

                            HitTestResult res = VisualTreeHelper.HitTest(content, e.GetPosition(content));
                            if (res != null)
                            {
                                contentToSwap = content;
                                break;
                            }
                        }
                    }

                    if (contentToSwap != null && !Equals(contentToSwap, this))
                    {
                        var containerPane = ContainerPane;
                        int myIndex = containerPane.Items.IndexOf(this);

                        ContainerPane.Items.RemoveAt(myIndex);

                        int otherIndex = containerPane.Items.IndexOf(contentToSwap);
                        containerPane.Items.RemoveAt(otherIndex);

                        containerPane.Items.Insert(otherIndex, this);

                        containerPane.Items.Insert(myIndex, contentToSwap);

                        containerPane.SelectedItem = this;

                        e.Handled = false;
                        //avoid ismouseDown = false call
                        return;
                    }
                    if (Math.Abs(ptMouseMove.X - StartDragPoint.X) > SystemParameters.MinimumHorizontalDragDistance
                        || Math.Abs(ptMouseMove.Y - StartDragPoint.Y) > SystemParameters.MinimumVerticalDragDistance)
                    {
                        ptRelativePosition = e.GetPosition(DragEnabledArea);

                        ResetIsMouseDownFlag();
                        OnDragStart(StartDragPoint, ptRelativePosition);
                        e.Handled = true;
                    }
                }
            }

            ResetIsMouseDownFlag();
        }

        protected virtual void OnDragMouseMove(object sender, MouseEventArgs e)
        {
        }

        protected virtual void OnDragMouseUp(object sender, MouseButtonEventArgs e)
        {
            isMouseDown = false;

            Debug.WriteLine("OnDragMouseUp" + e.ClickCount);
        }

        protected virtual void OnDragStart(Point ptMouse, Point ptrelativeMouse)
        {
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the FloatingWindowSize property.
        /// </summary>
        protected virtual void OnFloatingWindowSizeChanged(DependencyPropertyChangedEventArgs e)
        {
        }

        protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
        {
            base.OnGotKeyboardFocus(e);

            Debug.WriteLine(
                string.Format(
                    "[{0}].OnGotKeyboardFocus() Source={1} NewFocus={2} OldFocus={3}",
                    Name,
                    e.Source.GetType(),
                    e.NewFocus.GetType(),
                    e.OldFocus == null ? "<null>" : e.OldFocus.GetType().ToString()));

            if (Manager != null && IsKeyboardFocusWithin)
            {
                Manager.ActiveContent = this;
            }
        }

        protected override void OnInitialized(EventArgs e)
        {
            base.OnInitialized(e);

            CommandBindings.Add(new CommandBinding(ManagedContentCommands.Hide, OnExecuteCommand, OnCanExecuteCommand));
            CommandBindings.Add(new CommandBinding(ManagedContentCommands.Close, OnExecuteCommand, OnCanExecuteCommand));
            CommandBindings.Add(new CommandBinding(ManagedContentCommands.Show, OnExecuteCommand, OnCanExecuteCommand));
            CommandBindings.Add(
                new CommandBinding(ManagedContentCommands.Activate, OnExecuteCommand, OnCanExecuteCommand));
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the IsActiveContent property.
        /// </summary>
        protected virtual void OnIsActiveContentChanged(DependencyPropertyChangedEventArgs e)
        {
            if (IsActiveContent)
            {
                _lastActivation = DateTime.Now;
            }

            FocusContent();

            var parentPane = ContainerPane;
            if (parentPane != null)
            {
                parentPane.RefreshContainsActiveContentProperty();
                if (IsActiveContent)
                {
                    parentPane.SelectedItem = this;
                }
            }

            //for backward compatibility
            RaisePropertyChanged("IsActiveContent");

            if (IsActiveContentChanged != null)
            {
                IsActiveContentChanged(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the IsActiveDocument property.
        /// </summary>
        protected virtual void OnIsActiveDocumentChanged(DependencyPropertyChangedEventArgs e)
        {
            if ((bool) e.NewValue)
            {
                if (ContainerPane != null)
                {
                    ContainerPane.SelectedItem = this;
                }
            }

            var parentDocumentPane = ContainerPane as DocumentPane;
            if (parentDocumentPane != null)
            {
                parentDocumentPane.RefreshContainsActiveDocumentProperty();
            }

            //for backward compatibility
            RaisePropertyChanged("IsActiveDocumentChanged");

            if (IsActiveDocumentChanged != null)
            {
                IsActiveDocumentChanged(this, EventArgs.Empty);
            }
        }

        protected override void OnKeyDown(KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                if (ContainerPane != null)
                {
                    ContainerPane.SelectedItem = this;
                }
            }

            base.OnKeyDown(e);
        }

        protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
        {
            Debug.WriteLine(
                string.Format(
                    "[{0}].OnLostKeyboardFocus() Source={1} NewFocus={2} OldFocus={3}",
                    Name,
                    e.Source.GetType(),
                    e.NewFocus == null ? "<null>" : e.NewFocus.GetType().ToString(),
                    e.OldFocus == null ? "<null>" : e.OldFocus.GetType().ToString()));
            base.OnLostKeyboardFocus(e);
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the Manager property.
        /// </summary>
        protected virtual void OnManagerChanged(DockingManager oldValue, DockingManager newValue)
        {
        }

        protected override void OnMouseDown(MouseButtonEventArgs e)
        {
            base.OnMouseDown(e);

            if (!e.Handled)
            {
                Activate();
            }
        }

        protected override void OnVisualParentChanged(DependencyObject oldParent)
        {
            RaisePropertyChanged("ContainerPane");
            base.OnVisualParentChanged(oldParent);
        }

        protected void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        protected void ResetIsMouseDownFlag()
        {
            isMouseDown = false;
        }

        private static object OnCoerce_Icon(DependencyObject o, object value)
        {
            return value;
        }

        /// <summary>
        /// Handles changes to the FloatingWindowSize property.
        /// </summary>
        private static void OnFloatingWindowSizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((ManagedContent) d).OnFloatingWindowSizeChanged(e);
        }

        /// <summary>
        /// Handles changes to the IsActiveContent property.
        /// </summary>
        private static void OnIsActiveContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((ManagedContent) d).OnIsActiveContentChanged(e);
        }

        /// <summary>
        /// Handles changes to the IsActiveDocument property.
        /// </summary>
        private static void OnIsActiveDocumentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((ManagedContent) d).OnIsActiveDocumentChanged(e);
        }

        private void ManagedContent_Loaded(object sender, RoutedEventArgs e)
        {
            OnContentLoaded();
        }

        private void ManagedContent_Unloaded(object sender, RoutedEventArgs e)
        {
            OnContentUnloaded();
        }

        private void OnCanExecuteCommand(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = CanExecuteCommand(e.Command);
        }

        private void OnExecuteCommand(object sender, ExecutedRoutedEventArgs e)
        {
            if (e.Command == ManagedContentCommands.Show)
            {
                Show();
                e.Handled = true;
            }
            else if (e.Command == ManagedContentCommands.Hide)
            {
                e.Handled = Hide();
            }
            else if (e.Command == ManagedContentCommands.Close)
            {
                e.Handled = Close();
            }
            else if (e.Command == ManagedContentCommands.Activate)
            {
                Activate();
                e.Handled = true;
            }
        }

        #endregion
    }
}